# 导入 gensim 库，用于自然语言处理中的主题建模等任务
import gensim
# 导入 numpy 库，用于进行数值计算和处理数组
import numpy as np
# 从 gensim.utils 模块导入 simple_preprocess 函数，用于对文本进行简单的预处理
from gensim.utils import simple_preprocess
# 从 sklearn.decomposition 模块导入 TruncatedSVD 和 NMF 类，分别用于截断奇异值分解和非负矩阵分解
from sklearn.decomposition import TruncatedSVD, NMF
# 从 sklearn.feature_extraction.text 模块导入 TfidfVectorizer 类，用于将文本转换为 TF-IDF 特征矩阵
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入 pandas 库，用于数据处理和分析，主要处理表格数据
import pandas as pd
# 从 gensim 库导入 corpora 和 models 模块，分别用于创建语料库和训练模型
from gensim import corpora, models



# 读取预处理后的 CSV 文件，将其存储为 pandas 的 DataFrame 对象
medium = pd.read_csv('pre-processed.csv')

# 读取包含停用词的 CSV 文件，将其存储为 pandas 的 DataFrame 对象
stop_df = pd.read_csv('stopwords.csv')
# 将 DataFrame 中的停用词列转换为列表
stop_list = list(stop_df['stopword'])

# 创建 TfidfVectorizer 对象，设置停用词列表和 ngram 范围为 (1, 1)，即只考虑单个词
vectorizer = TfidfVectorizer(stop_words=stop_list, ngram_range=(1, 1))
# 对 medium 数据集中的 'text' 列进行 TF-IDF 转换，得到文档 - 词矩阵
doc_word = vectorizer.fit_transform(medium['text'])

# 定义一个函数，用于显示主题及其对应的关键词
def display_topics(model, feature_names, no_top_words, no_top_topics, topic_names=None):
    count = 0
    # 遍历模型的每个主题
    for ix, topic in enumerate(model.components_):
        # 如果已经显示的主题数量达到指定的最大主题数，则停止循环
        if count == no_top_topics:
            break
        # 如果没有提供主题名称，则打印默认的主题编号
        if not topic_names or not topic_names[ix]:
            print("\nTopic ", (ix + 1))
        # 如果提供了主题名称，则打印该主题名称
        else:
            print("\nTopic: '", topic_names[ix], "'")
        # 打印每个主题中权重最高的前 no_top_words 个关键词
        print(", ".join([feature_names[i]
                        for i in topic.argsort()[:-no_top_words - 1:-1]]))
        count += 1

# 用于进行 SVD 矩阵分解
svd = TruncatedSVD(8)
docs_svd = svd.fit_transform(doc_word)
display_topics(svd, vectorizer.get_feature_names_out(), 15, 8)

# 创建 NMF 模型对象，设置主题数量为 8
nmf = NMF(8)
# 对文档 - 词矩阵进行 NMF 分解，得到文档 - 主题矩阵
docs_nmf = nmf.fit_transform(doc_word)
# 调用 display_topics 函数，显示 NMF 模型得到的主题及其关键词
display_topics(nmf, vectorizer.get_feature_names_out(), 15, 8)

# 对 medium 数据集中的 'text' 列进行简单的预处理，如将大写转换为小写、去掉过长或过短的文本等
tokenized_docs = medium['text'].apply(simple_preprocess)
# 使用预处理后的文档创建一个字典，用于后续的语料库构建
dictionary = gensim.corpora.Dictionary(tokenized_docs)
# 对字典进行过滤，去掉出现次数低于 15 次的词，以及出现频率高于 50% 的词，保留最多 100000 个词
dictionary.filter_extremes(no_below=15, no_above=0.5, keep_n=100000)
# 将预处理后的文档转换为词袋格式的语料库
corpus = [dictionary.doc2bow(doc) for doc in tokenized_docs]

# 在 Windows 系统中，使用多进程时需要将代码放在 if __name__ == '__main__': 块中
if __name__ == '__main__':
    # 创建 LDA 多进程模型对象，设置语料库、主题数量、字典、迭代次数和工作进程数
    lda = models.LdaMulticore(corpus=corpus, num_topics=8, id2word=dictionary, passes=10, workers=4)
    # 获取 LDA 模型的主题信息
    topics = lda.print_topics()
    # 遍历并打印每个主题的信息
    for topic in topics:
        print(topic)

# 定义一个列表，包含后续要使用的列名
column_names = ['title', 'url', 'allTags', 'readingTime', 'author', 'Tech',
                'Modeling', 'Chatbots', 'Deep Learning', 'Coding', 'Business',
                'Careers', 'NLP', 'sum']
# 计算文档 - 主题矩阵中每一行的和，即每个文档在各个主题上的可能性总和
topic_sum = pd.DataFrame(np.sum(docs_nmf, axis = 1))
# 将文档 - 主题矩阵转换为 pandas 的 DataFrame 对象
doc_topic_df = pd.DataFrame(data = docs_nmf)
# 将 medium 数据集中的部分列、文档 - 主题矩阵和主题可能性总和列按列方向拼接成一个新的 DataFrame
doc_topic_df = pd.concat([medium[['title', 'url', 'allTags', 'readingTime', 'author']], doc_topic_df, topic_sum], axis = 1)
# 为新的 DataFrame 设置列名
doc_topic_df.columns = column_names
# 筛选出主题可能性总和不为 0 的文档，即剔除那些在所有主题上可能性都为 0 的文档
doc_topic_df = doc_topic_df[doc_topic_df['sum'] != 0]
# 删除 DataFrame 中的 'sum' 列
doc_topic_df.drop(columns = 'sum', inplace = True)

# 重置 DataFrame 的索引，丢弃原索引
doc_topic_df.reset_index(drop = True, inplace = True)
# 将处理后的 DataFrame 保存为 CSV 文件，不保存索引
doc_topic_df.to_csv('tfidf_nmf_8topics.csv', index = False)
# 查看处理后的 DataFrame 的前几行
doc_topic_df.head()